package com.atguigu.lease.web.admin.service.impl;

import com.atguigu.lease.common.constant.RedisConstant;
import com.atguigu.lease.common.context.LoginUser;
import com.atguigu.lease.common.context.LoginUserContext;
import com.atguigu.lease.common.exception.LeaseException;
import com.atguigu.lease.common.result.ResultCodeEnum;
import com.atguigu.lease.common.utils.JwtUtil;
import com.atguigu.lease.model.entity.SystemUser;
import com.atguigu.lease.model.enums.BaseStatus;
import com.atguigu.lease.web.admin.service.LoginService;
import com.atguigu.lease.web.admin.service.SystemUserService;
import com.atguigu.lease.web.admin.vo.login.CaptchaVo;
import com.atguigu.lease.web.admin.vo.login.LoginVo;
import com.atguigu.lease.web.admin.vo.system.user.SystemUserInfoVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import io.jsonwebtoken.Claims;
import io.netty.util.internal.StringUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private SystemUserService systemUserService;

    //获取图形验证码
    @Override
    public CaptchaVo getCaptcha() {
        //生成一个验证码对象，长度为130，高度为48，验证码位数为4
        SpecCaptcha captcha = new SpecCaptcha(130, 48,4);
        //这里设置验证码的样式，这里使用默认样式
        captcha.setCharType(Captcha.TYPE_DEFAULT);
        //获取验证码的文本内容值，并且转换成小写
        String code = captcha.text().toLowerCase();
        //自定义创建一个独一无二的keyName，作为验证码存储在redis服务器的键
        String key = RedisConstant.ADMIN_LOGIN_PREFIX + UUID.randomUUID();
        //现在有了code和key，将这个验证码存入redis服务器中，并且设置过期时间
        redisTemplate.opsForValue().set(key,code,RedisConstant.ADMIN_LOGIN_CAPTCHA_TTL_SEC, TimeUnit.SECONDS);
        //获取验证码的图像信息，并且转换为base64编码
        //前端接收到这个base64编码，再通过canvas标签绘制出来
        String image = captcha.toBase64();
        return new CaptchaVo(image,key);
    }

    //登录业务逻辑实现，返回tocken
    @Override
    public String login(LoginVo loginVo) {
        //1.校验返回过来的验证码是否为空
        if (!StringUtils.hasText(loginVo.getCaptchaCode())){
            throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_NOT_FOUND);
        }
        //2.校验验证码
        String code = redisTemplate.opsForValue().get(loginVo.getCaptchaKey());
        //2.1 如果从redis数据库里查到的code是null，那么说明验证码过期了
        if (code == null){
            throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_EXPIRED);
        }
        //2.2 判断验证码是否正确
        if (!code.equals(loginVo.getCaptchaCode())){
            throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_ERROR);
        }
        //3.校验用户是否存在(即数据库中是否存在这个用户名，此处并非直接校验密码)
        LambdaQueryWrapper<SystemUser> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SystemUser::getUsername,loginVo.getUsername());
        SystemUser user = systemUserService.getOne(queryWrapper);
        if (user == null){
            throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_NOT_EXIST_ERROR);
        }
        //4.校验用户是否合法(是否账号被封禁)
        if (user.getStatus() == BaseStatus.DISABLE){
            throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_DISABLED_ERROR);
        }
        //5.校验用户的密码是否正确(数据库中的密码是加密过后的，所以要把传来的密码加密后和数据库中的做对比)
        if (!user.getPassword().equals(DigestUtils.md5Hex(loginVo.getPassword()))){
            throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_ERROR);
        }
        //6.通过全部校验，返回tocken字符串
        return JwtUtil.createToken(user.getId(), user.getUsername());
    }

    //获得登录用户的个人信息
    @Override
    public SystemUserInfoVo findSystemUserInfoVo() {
        //TODO 此处是我们第二次解析token，这里存在重复解析JWT的问题
        // 我们可以尝试使用ThreadLocal 线程本地变量来优化此处的逻辑
        //将前端传来的token信息，用工具类解析后，获取需要的信息
        /*
        Claims claims = JwtUtil.parseToken(token);
        //获取对应的用户的id
        Long userId = claims.get("userId", Long.class);
        */
        //TODO 【英雄登场】使用ThreadLocal解决多次解析的问题
        LoginUser loginUser = LoginUserContext.getLoginUser();
        //通过从ThreadLocal中获取对应的登录用户对象，再从中获取id，这样就避免了再次解析JWT的问题
        // PS:虽然说多次解析JWT不是什么大问题，但是不够优雅，有点low
        Long userId = loginUser.getUserId();
        //根据id获取对应的用户对象
        SystemUser systemUser = systemUserService.getById(userId);
        //将SystemUser转换成SystemUserInfoVo
        SystemUserInfoVo systemUserInfoVo = new SystemUserInfoVo();
        BeanUtils.copyProperties(systemUser,systemUserInfoVo);
        //将将SystemUser转换成SystemUserInfoVo对象返回
        return systemUserInfoVo;
    }
}
